#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/serio.h>
#include <linux/clk.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <plat/regs-adc.h>

#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);}
#else
#define DPRINTK(x...) (void)(0)
#endif
// 决定在设备目录产生的设备名字 fd = open("/dev/evb_adc", 0);
#define DEVICE_NAME "evb_adc"

// 硬件操作的虚拟地址
void static __iomem *adc_base;
// 从平台时钟队列中获取ADC的时钟
static struct clk *adc_clk;
// 芯片上的ADC模块除了作为普通ADC之外，还作为触摸屏控制器，必须避免竞态发生，这里使用互斥信号量保证ADC资源互斥访问
DECLARE_MUTEX(ADC_LOCK);
// 定义并初始化一个等待队列，对ADC资源进行阻塞访问
static DECLARE_WAIT_QUEUE_HEAD(adc_wait_queue);
// AD转换进行中标记，用于向上层反馈信息
static volatile int adc_event_flag = 0;
// 保存ADC转换结果
static int reg_value;

static struct file_operations adc_fops;
// misc设备结构体
static struct miscdevice misc_adc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &adc_fops,
};

// IRQ HANDLER
static irqreturn_t adc_irq(int irq, void *dev_id)
{
	// 保证了应用程序读取一次这里就读取AD转换的值一次，避免应用程序读取一次后发生多次中断多次读取AD转换值
	if(!adc_event_flag){
		// 获取转换后寄存器有效数据
		reg_value = readl(adc_base + S3C2410_ADCDAT0)&0x3ff;
		
		adc_event_flag = 1;
		// 唤醒等待队列
		wake_up_interruptible(&adc_wait_queue);
	}
	return IRQ_HANDLED;
}

static void start_adc_trans(void)
{
	unsigned int tmp;
    tmp = (1 << 14) | (255 << 6) | (0 << 3);/* 0 1 00000011 000 0 0 0 */
    writel(tmp, adc_base + S3C2410_ADCCON); /*AD预分频器使能、模拟输入通道设为AIN0*/
    tmp = readl(adc_base + S3C2410_ADCCON);
    tmp = tmp | (1 << 0);                 /* 0 1 00000011 000 0 0 1 */
    writel(tmp, adc_base + S3C2410_ADCCON); /*AD转换开始*/
}

// 
static int adc_open(struct inode *inode, struct file *file)
{
	int ret;
	DPRINTK(KERN_INFO"%s in\n", __FUNCTION__);
	//ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, 1);
	//if(ret){
	//	printk(KERN_ERR"IRQ %d error %d \n", IRQ_ADC, ret);
	//	ret = -EINVAL;
	//}
	return 0;
}
#if 0
static int adc_close(struct inode *inode, struct file *file)
{
	printk(KERN_INFO"%s\n", __FUNCTION__);
	return 0;
}
#endif

static ssize_t adc_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
	DPRINTK(KERN_INFO"%s in\n", __FUNCTION__);
	// 获取信号量，即加锁
	if(down_trylock(&ADC_LOCK)){
		return -EBUSY;
	}
	DPRINTK(KERN_INFO"%s in ++++1111\n", __FUNCTION__);
	if(!adc_event_flag){ //表示还没有AD转换后的数据，不可读取
		if(file->f_flags & O_NONBLOCK){ // 若用户采取非阻塞方式读取，返回错误信息
			return -EAGAIN;
		}
		else{
			start_adc_trans(); // 启动ADC转换，参考芯片手册，转换完成会触发中断
			wait_event_interruptible(adc_wait_queue, adc_event_flag); // 等待队列进入睡眠，中断会唤醒
		}
	}
	DPRINTK(KERN_INFO"%s in ++++2222\n", __FUNCTION__);
	// 到这里说明已经转换完成
	adc_event_flag = 0;
	// 将数据传递给用户空间
	copy_to_user(buffer, (char *)&reg_value, sizeof(reg_value));
	DPRINTK(KERN_INFO"%s in ++++3333\n", __FUNCTION__);
	up(&ADC_LOCK);
	
	return sizeof(reg_value);
}

static int adc_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO"%s\n", __FUNCTION__);
	return 0;
}
// 设备操作结构体实现 
static struct file_operations adc_fops = {
	.owner = THIS_MODULE,
	.open = adc_open,
	//.close = adc_close,
	.read = adc_read,
	.release = adc_release,
};

static int __init adc_init(void)
{
	int ret;
	
	DPRINTK(KERN_INFO"%s in\n", __FUNCTION__);
	/*从平台时钟队列中获取ADC的时钟，这里为什么要取得这个时钟，因为ADC的转换频率跟时钟有关。
	  系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中
		{
			.name		= "adc",
			.id		= -1,
			.parent		= &clk_p,
			.enable		= s3c2410_clkcon_enable,
			.ctrlbit	= S3C2410_CLKCON_ADC,
		}
	*/
	adc_clk = clk_get(NULL, "adc");
	
	if(!adc_clk){
		printk(KERN_ERR"failed to find adc clock\n");
		return -ENOENT;
	}
	// 最终调用的就是在结构体当中指定的s3c2410_clkcon_enable
	clk_enable(adc_clk);
    /*将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址，ioremap定义在io.h中。注意：IO空间要映射后才能使用，以后对虚拟地址的操作就是对IO空间的操作,
     S3C2410_PA_ADC是ADC控制器的基地址，定义在mach-s3c2410/include/mach/map.h中，0x20是虚拟地址长度大小*/
	adc_base = ioremap(S3C2410_PA_ADC,0x20);
	if(NULL == adc_base){
		printk(KERN_ERR"failed to remap adc register block\n");
		ret = -ENOMEM;
	}
	
	/*申请ADC中断服务，这里使用的是共享中断:IRQF_SHARED,为什么要使用共享中断，因为在触摸屏驱动中也使用了这个中断号。
	中断服务程序为:adc_irq在下面实现，IRQ_ADC是ADC的中断号，
	这里注意：申请中断函数的最后一个参数一定不能为NULL，否则中断申请会失败，
	如果中断服务程序中用不到这个参数，就随便给个值就好了，我这里就给个1*/	
	ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, 1);
	if(ret){
		printk(KERN_ERR"IRQ %d error %d \n", IRQ_ADC, ret);
		ret = -EINVAL;
	}
	//把看ADC注册成为misc设备
	ret = misc_register(&misc_adc);
	if(ret){
		printk(KERN_ERR"cannot reigster miscdev on minor = %d (%d)\n", MISC_DYNAMIC_MINOR, ret);
		goto err_exit;
	}
	return ret;
	
err_exit:
	iounmap(adc_base);
	clk_disable(adc_clk);
	clk_put(adc_clk);
	return ret;
}

static void __exit adc_exit(void)
{
	free_irq(IRQ_ADC, 1);
	iounmap(adc_base);
	if(adc_clk){
		clk_disable(adc_clk);
		clk_put(adc_clk);
		adc_clk = NULL;
	}
	misc_deregister(&misc_adc);
}

module_init(adc_init);
module_exit(adc_exit);


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("huntley.bian@foxmail.com");
MODULE_DESCRIPTION("evb2440 ADC Driver");